#include "Machine.h"
#include "Transfer.h"
#include "CharType.h"
#include "Tape.h"
#include "CodeTable.h"
#include "Tags.h"
#include "EnvStrings.h"
#include "Logger.h"

CodeTable Encoder;

Machine::Machine(DraftPtr draft, const ChannelMap& channels)
: _draft(draft)
, _channels(channels)
, _ctrlTape(new Tape(CharType::CONTROL))
, _actionFactory(new ActionFactory(this))
, _currentState(new State)
{
    CreateEnvStrings();
    connect(this, SIGNAL(dataProcessed()), this, SLOT(ProcessData()), Qt::QueuedConnection);
    RegisterGlobals();
    RegisterStates();
    TuneUpChannels();
	TuneUpTapes();
}

void Machine::CreateEnvStrings()
{
    _envStrings[LatestSaved];
    _envStrings[LatestRead];
}

void Machine::TuneUpTapes()
{
    connect(_ctrlTape.get(), SIGNAL(NewData()), SLOT(ProcessData()), Qt::QueuedConnection);

    Q_FOREACH(const QString& channel, _channels.keys())
    {
		_tapes[channel] = TapePtr(new Tape);
		_tapes[channel]->SetCharAgent(_channels[channel]);
		connect(_tapes[channel].get(), SIGNAL(NewData()), SLOT(ProcessData()), Qt::QueuedConnection);
    }
}

void Machine::TuneUpChannels()
{
    Q_FOREACH(ISimpleChannelPtr channel, _channels.values())
    {
        connect(channel.get(), SIGNAL(keyValueSignal(const QString&, const QString&)), SLOT(UpdateEnvString(const QString&, const QString&)), Qt::DirectConnection);
        connect(channel.get(), SIGNAL(disconnected()), this, SIGNAL(stopped()));
    }
}

void Machine::RegisterGlobals()
{
    Q_FOREACH(const QString& global, _draft->globals.keys())
        _globals[global] = _draft->globals[global].toAscii();
}

void Machine::RegisterStates()
{    
    RegisterProcedures();
    RegisterTransfers();
    SetStatesParent();
}

void Machine::SetStatesParent()
{
    Q_FOREACH(StatePtr state, _registeredStates.values())
        state->SetMachine(this);
}

StatePtr Machine::GetRegisteredState(const QString& state)
{
	return _registeredStates.contains(state) ? _registeredStates[state] : _registeredStates[state] = StatePtr(new State);
}

TransferPtr Machine::GetBlankTransfer()
{
    TransferPtr transfer(new Transfer);
    connect(transfer.get(), SIGNAL(NextState(StatePtr)), SLOT(ChangeState(StatePtr)), Qt::DirectConnection);

    return transfer;
}

void Machine::RegisterProcedures() 
{
    Q_FOREACH(const QString& state, _draft->state2procedures.keys()) 
    {
		const StringMapList& actionsList = _draft->state2procedures[state];
        for (int index = 0; index != actionsList.size(); ++index)
        {
			ActionPtr action = _actionFactory->MakeAction(actionsList[index][tag::AAction], actionsList[index]);
            GetRegisteredState(state)->AddAction(action);
        }
    }
}

void Machine::RegisterTransfers() 
{
    Q_FOREACH(const QString& state, _draft->state2transfers.keys())
    { 
        Q_FOREACH(const QString& transferUID, _draft->state2transfers[state])
        {
            TransferPtr transfer = GetBlankTransfer();

            const StringMapList& actionsList = _draft->transfer2actions[transferUID];
            for (int index = 0; index != actionsList.size(); ++index)
                transfer->AddAction(_actionFactory->MakeAction(actionsList[index][tag::AAction], actionsList[index]));

            CharTypeList charList;
            const QStringList& characterList = _draft->transfer2char[transferUID];
            Q_FOREACH(const QString& character, characterList)
                charList << CharTypePtr(new CharType(character));   

            transfer->SetDestination(GetRegisteredState(_draft->transfer2state[transferUID]));

            GetRegisteredState(state)->RegisterTransfer(transfer, charList);
        }

        RegisterTerminalTransfer(_registeredStates[state], CharTypePtr(new CharType("ABORT")), GetRegisteredState(tag::AExit));
    }
}

void Machine::RegisterTerminalTransfer(StatePtr state, CharTypePtr character, StatePtr terminal)
{
    TransferPtr transfer = GetBlankTransfer();
    transfer->SetDestination(terminal);
    state->RegisterTransfer(transfer, CharTypeList() << character);
}

Machine::GlobalMap& Machine::Globals()
{
    return _globals;
}

const Machine::GlobalMap& Machine::Globals() const
{
    return _globals;
}

StatePtr Machine::CurrentState()
{
    return _currentState;
}

void Machine::Start()
{
    logger.addn("Machine::Start()");

    RegisterGlobals();

    _ctrlTape->Reset();
    
    Q_FOREACH(TapePtr tape, _tapes.values())
        tape->Reset();
    
    ChangeState(_registeredStates[_draft->initialState]);
}

void Machine::Stop(bool gentle)
{
    _ctrlTape->PutChar(Encoder.Encode(gentle ? "EXIT" : "ABORT"));
}

bool Machine::IsTerminalState(StatePtr state)
{
    return _registeredStates[tag::AExit]  == state;
}

void Machine::ChangeState(StatePtr state)
{ 
    _currentState->CancelActions();
    _currentState = state;

    !IsTerminalState(_currentState) ? PrepareState() : Finish();
}

void Machine::Finish()
{
    logger.addn("Machine::Finish()");
    emit stopped();
}

void Machine::PrepareState()
{
    _ctrlTape->SetCharsLookUp(_currentState->Chars());
    
    Q_FOREACH(TapePtr tape, _tapes.values())
        tape->SetCharsLookUp(_currentState->Chars());
    
    _currentState->PerformActions();
}

void Machine::ProcessData()
{
    CharTypePtr next;
    
    if ((next = NextCtrl()) || (next = NextData()))
    {
        _currentState->ProcessChars(next);
        emit dataProcessed();
    }
}

CharTypePtr Machine::NextCtrl()
{
    return _ctrlTape->Next();
}

CharTypePtr Machine::NextData()
{
    CharTypePtr result;

    Q_FOREACH(TapePtr tape, _tapes.values())
        if (result = tape->Next()) break;        
    
    return result;
}

ISimpleChannelPtr Machine::Channel(const QString& channel)
{
	return _channels.contains(channel) ? _channels[channel] : ISimpleChannelPtr();
}

TapePtr Machine::ChannelTape(const QString& channel)
{
    return _tapes.contains(channel) ? _tapes[channel] : TapePtr();
}

TapePtr Machine::ControlTape()
{
    return _ctrlTape;
}

void Machine::PlaceTimeoutChar()
{
    _ctrlTape->PlaceTimeoutChar();
}

bool Machine::IsEnvString(const QString& key)
{
    return _envStrings.contains(key);
}

QStringRef Machine::EnvStringRef(const QString& key)
{
    QMutexLocker lock(&_envChangeLock);
    return IsEnvString(key) ? &_envStrings[key] : QStringRef();
}

void Machine::UpdateEnvString(const QString& key, const QString& value)
{
    QMutexLocker lock(&_envChangeLock);
    if (IsEnvString(key))
        _envStrings[key] = value;
}
